#!/usr/bin/env python
# -*- coding: utf-8 -*-

import threading
from flask import session, request, redirect, url_for, render_template, jsonify
import transaction
from functools import wraps
from typing import Optional
from datetime import timedelta, datetime
from common.config import DatabaseConfig
from web_server.device_proxy import DeviceProxy
from web_server.models.db_client import DBClient
from web_server.models.user_mgr import UserMgr
from web_server.models.role_mgr import RoleMgr
from web_server.models.group_mgr import GroupMgr
from web_server.models.project_mgr import ProjectMgr
from web_server.models.perm_mgr import PermMgr
from web_server.models.perm import PermType
from common.utils import LogUtil, api
from web_server.models.activities.activity_factory import ActivityFactory

logger = LogUtil.get_logger(__name__)

# 密码认证装饰器
def login_required(f):
    """登录验证装饰器
    
    用于保护需要登录才能访问的路由。如果用户未登录，将重定向到登录页面。
    
    参数：
        f: 被装饰的函数
        
    返回：
        decorated_function: 装饰后的函数
    """
    @wraps(f)
    def decorated_function(*args, **kwargs):
        if 'username' not in session:
            logger.warning(f'未授权访问: {request.path}, IP: {request.remote_addr}')
            return redirect(url_for('login', next=request.url))
        logger.debug(f'用户 {session.get("username")} 访问受保护路由: {request.path}')
        return f(*args, **kwargs)
    return decorated_function

class HttpService:
    """代理服务类，作为Web后端API和反向代理服务器"""
    
    def __init__(self, stop_cb, device_proxy: DeviceProxy):
        """初始化代理服务
        
        Args:
            stop_cb: 事件回调函数，用于通知服务管理器服务状态
            device_proxy (DeviceProxy): 设备代理
        """
        self.__apis = {}
        self.__stop_cb = stop_cb
        self.__device_proxy = device_proxy
        self.__db_client = None
        self.__user_mgr = None
        self.__role_mgr = None
        self.__group_mgr = None
        self.__project_mgr = None
        self.__perm_mgr = None
        self.__service_thread = None

        self.__register_apis()
        self.__register_login_routes()
        self.__register_index_routes()
        self.__register_device_routes()
        self.__register_project_routes()

        self.__db_client = DBClient(
            DatabaseConfig.DB_TYPE,
            DatabaseConfig.DB_HOST,
            DatabaseConfig.DB_PORT,
            DatabaseConfig.DB_PATH)
        self.__user_mgr = UserMgr(self.__device_proxy, self.__db_client)
        self.__role_mgr = RoleMgr(self.__device_proxy, self.__db_client)
        self.__group_mgr = GroupMgr(self.__device_proxy, self.__db_client)
        self.__project_mgr = ProjectMgr(self.__device_proxy, self.__db_client)
        self.__perm_mgr = PermMgr(self.__device_proxy, self.__db_client)

    def __register_apis(self):
        """自动注册所有带有@api装饰器的方法"""
        for name in dir(self.__class__):
            if name.startswith('_'):
                continue
            try:
                method = getattr(self.__class__, name)
                if callable(method) and hasattr(method, '_is_api'):
                    api_name = getattr(method, '_api_name', name)
                    self.__apis[api_name] = getattr(self, name)
                    logger.debug(f"Registered API: {api_name} -> {name}")
            except Exception as e:
                # 跳过可能引起问题的属性
                logger.error(f"Error registering API {name}: {e}")
                continue

    def __register_login_routes(self):
        app = self.__device_proxy.app
        session = self.__device_proxy.session
    
        @app.route('/login', methods=['GET', 'POST'])
        def login():
            """用户登录处理
            
            处理用户登录请求。GET请求返回登录页面，POST请求处理登录表单提交。
            
            请求方法：
                GET: 显示登录页面
                POST: 处理登录表单提交
                
            表单参数：
                username: 用户名
                password: 密码
                remember: 记住登录状态（可选）
                
            返回：
                GET: 登录页面
                POST成功: 重定向到首页或请求的下一页
                POST失败: 带有错误信息的登录页面
            """
            logger.debug('用户访问登录页面')
            error = None
            if request.method == 'POST':
                username = request.form['username']
                password = request.form['password']
                remember = 'remember' in request.form
                logger.debug(f'用户 {username} 尝试登录，记住登录状态: {remember}')
                # 验证用户名和密码
                if self.login(username, password, remember):
                    # 重定向到首页或请求的下一页
                    next_page = request.args.get('next')
                    logger.info(f'用户 {username} 登录成功')
                    return redirect(next_page or url_for('index'))
                else:
                    logger.warning(f'用户 {username} 登录失败：用户名或密码错误')
                    error = '用户名或密码错误'
            return render_template('login.html', error=error)

        @app.route('/logout')
        def logout():
            """用户登出处理
            
            处理用户登出请求，清除会话并重定向到登录页面。
            
            请求方法：
                GET
                
            返回：
                重定向到登录页面
            """
            username = session.get('username', '未知用户')
            logger.info(f'用户 {username} 登出系统')
            # 清除会话
            self.logout()
            return redirect(url_for('login'))

    def __register_index_routes(self):
        app = self.__device_proxy.app
        session = self.__device_proxy.session

        @app.route('/')
        @login_required
        def index():
            """首页
            
            显示应用程序的首页。需要用户登录。
            
            请求方法：
                GET
                
            返回：
                渲染后的index.html模板
            """
            logger.debug(f'用户 {session.get("username")} 访问首页')
            return render_template('index.html')

    def __register_device_routes(self):
        app = self.__device_proxy.app
        session = self.__device_proxy.session

        @app.route('/api/devices', methods=['GET', 'POST'])
        def call_device_api():
            if request.method == 'POST':
                args = request.json
                api = args.pop('api', None) # 从请求体中获取api参数
            else:
                args = request.args.to_dict()
                api = args.pop('api', None) # 从查询参数中获取api参数
            logger.debug(f'用户 {session.get("username")} 调用设备API: {api}, 参数：{args}')
            response = self.call(api, args)
            logger.debug(f'用户 {session.get("username")} 调用设备API: {api}, 响应：{response}')
            return jsonify(response['content']), response['code']

    def __register_project_routes(self):
        app = self.__device_proxy.app
        session = self.__device_proxy.session

        @app.route('/api/projects', methods=['GET', 'POST'])
        @login_required
        def call_project_api():
            if request.method == 'POST':
                args = request.json
                api = args.pop('api', None) # 从请求体中获取api参数
            else:
                args = request.args.to_dict()
                api = args.pop('api')
            logger.debug(f'用户 {session.get('username')} 调用项目API: {api}, 参数：{args}')
            response = self.call(api, args)
            logger.debug(f'用户 {session.get('username')} 调用项目API: {api}, 响应：{response}')
            return jsonify(response['content']), response['code']

        @app.route('/project/<string:owner>/<string:name>')
        @login_required
        def open_project(owner, name):
            args = {
                'owner': owner,
                'name': name
            }
            logger.debug(f'用户 {session.get("username")} 打开项目 {owner}/{name}')
            response = self.call('open_project', args)
            if response['code'] >= 400:
                flash(response['content']['message'], 'error')
                logger.error(f'用户 {session.get("username")} 打开项目 {owner}/{name} 失败，原因：{response["content"]["message"]}')
                return redirect(url_for('index'))
            project_data = response['content']
            logger.debug(f'用户 {session.get("username")} 打开项目 {owner}/{name} 成功，项目数据：{project_data}')
            return render_template('project.html', project=project_data)

        @app.route('/project/<string:owner>/<string:name>/<string:test>')
        @login_required
        def open_test_case(owner, name, test):
            args = {
                'owner': owner,
                'name': name,
                'test': test
            }
            logger.debug(f'用户 {session.get("username")} 打开测试用例: {owner}/{name}/{test}, 参数：{args}')
            response = self.call('open_test_case', args)
            logger.debug(f'用户 {session.get("username")} 打开测试用例: {owner}/{name}/{test}, 响应：{response}')
            return jsonify(response['content']), response['code']

    def __del__(self):
        """销毁代理服务"""
        self.destroy()

    def destroy(self):
        if self.__perm_mgr:
            self.__perm_mgr.destroy()
            self.__perm_mgr = None
        if self.__project_mgr:
            self.__project_mgr.destroy()
            self.__project_mgr = None
        if self.__group_mgr:
            self.__group_mgr.destroy()
            self.__group_mgr = None
        if self.__role_mgr:
            self.__role_mgr.destroy()
            self.__role_mgr = None
        if self.__user_mgr:
            self.__user_mgr.destroy()
            self.__user_mgr = None
        if self.__db_client:
            self.__db_client.destroy()
            self.__db_client = None
        self.__device_proxy = None

    def start(self):
        """启动代理服务"""
        app = self.__device_proxy.app
        socketio = self.__device_proxy.socketio
        
        def run_service():
            try:
                socketio.run(
                        app,
                        host=app.config['HOST'],
                        port=app.config['PORT'],
                        debug=app.config['DEBUG'],
                        use_reloader=app.config['USE_RELOADER'])
            finally:
                logger.error(f'HTTP服务运行错误')
                # 通知服务管理器HTTP服务已停止
                self.__stop_cb('http')
                self.destroy()
                logger.info('HTTP服务已停止')
        
        # 在子线程中启动服务
        self.__service_thread = threading.Thread(target=run_service, daemon=True)
        self.__service_thread.start()
        
        return self.__service_thread and self.__service_thread.is_alive()
    
    def call(self, api_name: str, args: dict) -> dict:
        api_func = self.__apis.get(api_name, None)
        if api_func is None:
            return {
                'code': 500, 
                'content': {
                    'message': f'Unsupported API: {api_name}'
                }
            }
        return api_func(**args)

############################################## 登录/注销 API ##############################################
    @api()
    def login(self, username, password, remember):
        user = self.__user_mgr.get_user(username)
        if user is None:
            return False
        if not user.check_password(password):
            return False
        # 登录成功，设置会话
        self.__device_proxy.session['username'] = username
        self.__device_proxy.session['user_id'] = user.username  # 使用username作为用户ID
        self.__device_proxy.session['email'] = user.email
        self.__device_proxy.session['is_admin'] = user.is_admin
        self.__device_proxy.session['is_active'] = user.is_active
        self.__device_proxy.session['roles'] = list(user.roles) if user.roles else []
        self.__device_proxy.session['login_time'] = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
        self.__device_proxy.session['last_activity'] = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
        
        # 更新用户最后登录时间
        user.update_last_login()
        
        # 如果选择了"记住我"，设置会话过期时间为30天
        if remember:
            self.__device_proxy.session.permanent = True
            self.__device_proxy.app.permanent_session_lifetime = timedelta(days=30)
        return True

    @api()
    def logout(self):
        self.__device_proxy.session.clear()

############################################## 项目 API ##############################################
    @api()
    def load_projects(
            self,
            search: Optional[str] = None,
            owner: Optional[str] = None,
            type: Optional[str] = None,
            board: Optional[str] = None,
            product: Optional[str] = None,
            platform: Optional[str] = None,
            version: Optional[str] = None,
            start_date: Optional[str] = None,
            end_date: Optional[str] = None,
            fuzzy_search: bool = True,
        ):
        """获取项目列表
        Args:
            search (str, optional): 搜索关键字
            owner (str, optional): 项目所有者
            type (str, optional): 项目类型
            board (str, optional): 项目板型
            product (str, optional): 产品名称
            platform (str, optional): 平台名称
            version (str, optional): 版本号
            start_date (str, optional): 开始日期
            end_date (str, optional): 结束日期
            fuzzy_search (bool, optional): 是否模糊搜索，默认为True
        Returns:
            dict: 包含code和content的响应格式
        """
        current_user = self.__user_mgr.current_user()
        if current_user is None:
            return { 'code': 500, 'content': {'message': '无法获取当前用户信息' } }
        username = current_user.username
        # 获取用户的项目列表
        project_paths = self.__perm_mgr.get_project_paths(current_user, [PermType.READ])
        logger.debug(f'user {username} has projects: {project_paths}')
        projects = [self.__project_mgr.get_project(path) for path in project_paths]
        
        # 定义项目过滤器函数
        def filter_project_by_name(projects, search, fuzzy_search = True):
            if search is None:
                return projects
            if fuzzy_search:
                return [project for project in projects if search in project.name]
            else:
                return [project for project in projects if project.name == search]
        def filter_project_by_owner(projects, owner):
            if owner is None:
                return projects
            return [project for project in projects if project.owner == owner]
        def filter_project_by_type(projects, type):
            if type is None:
                return projects
            return [project for project in projects if project.type == type]
        def filter_project_by_board(projects, board):
            if board is None:
                return projects
            return [project for project in projects if project.board == board]
        def filter_project_by_product(projects, product):
            if product is None:
                return projects
            return [project for project in projects if project.product == product]
        def filter_project_by_platform(projects, platform):
            if platform is None:
                return projects
            return [project for project in projects if project.platform == platform]
        def filter_project_by_version(projects, version):
            if version is None:
                return projects
            return [project for project in projects if project.version == version]
        def filter_project_by_date(projects, start_date, end_date):
            if start_date is None or end_date is None:
                return projects
            start_date = datetime.strptime(start_date, "%Y-%m-%d")
            end_date = datetime.strptime(end_date, "%Y-%m-%d")
            return [project for project in projects if start_date <= project.created <= end_date]
        # 过滤项目
        projects = filter_project_by_name(projects, search, fuzzy_search)
        projects = filter_project_by_owner(projects, owner)
        projects = filter_project_by_type(projects, type)
        projects = filter_project_by_board(projects, board)
        projects = filter_project_by_product(projects, product)
        projects = filter_project_by_platform(projects, platform)
        projects = filter_project_by_version(projects, version)
        projects = filter_project_by_date(projects, start_date, end_date)
        # 转换为字典格式
        projects = [project.to_dict() for project in projects]
        return { 'code': 200, 'content': projects }

    @api()
    def delete_projects(self, owner, name):
        """删除项目
        Args:
            owner (str): 项目所有者
            name (str): 项目名称
        Returns:
            bool: 删除成功返回True，否则返回False
        """
        current_user = self.__user_mgr.current_user()
        if current_user is None:
            return { 'code': 500, 'content': {'message': '无法获取当前用户信息' } }
        if not self.__perm_mgr.check_permission(current_user, f'{owner}/{name}', [PermType.DELETE]):
            return { 'code': 403, 'content': { 'message': '用户没有权限删除项目' } }
        project = self.__project_mgr.get_project(f'{owner}/{name}')
        savepoint = transaction.savepoint()
        try:
            for username in project.authorized_users:
                user = self.__user_mgr.get_user(username)
                if user is not None:
                    self.__perm_mgr.revoke_user_permissions(user, f'{owner}/{name}')
            for rolename in project.authorized_roles:
                role = self.__role_mgr.get_role(rolename)
                if role is not None:
                    self.__perm_mgr.revoke_role_permissions(role, f'{owner}/{name}')
            for groupname in project.authorized_groups:
                group = self.__group_mgr.get_group(groupname)
                if group is not None:
                    self.__perm_mgr.revoke_group_permissions(group, f'{owner}/{name}')
            project.authorized_users.clear()
            project.authorized_roles.clear()
            project.authorized_groups.clear()
            self.__project_mgr.delete_project(f'{owner}/{name}')
            transaction.commit()
            return { 'code': 200, 'content': {} }
        except Exception as e:
            logger.error(f'删除项目失败: {e}')
            savepoint.rollback()
            return { 'code': 500, 'content': {'message': '删除项目事务提交失败' } }

    @api()
    def import_projects(self, path):
        logger.error(f'method is empty: {__name__}.{__class__}.{__func__.__name__}')

    @api()
    def export_projects(self, path):
        logger.error(f'method is empty: {__name__}.{__class__}.{__func__.__name__}')

    @api()
    def new_project(self, name: str, type: str, board: str, product: str, platform: str, version: str, description: str):
        """新建项目

        Args:
            name (str): 项目名称
            type (str): 项目类型
            board (str): 项目板型
            product (str): 产品名称
            platform (str): 平台名称
            version (str): 版本号
            description (str): 项目描述
        Returns:
            Project: 新建的项目对象
        """        
        current_user = self.__user_mgr.current_user()
        if current_user is None:
            return { 'code': 500, 'content': {'message': '无法获取当前用户信息' } }
        username = current_user.username
        
        # 创建事务保存点
        savepoint = transaction.savepoint()
        try:
            # 在事务中执行所有操作
            project = self.__project_mgr.new_project(
                owner=username,
                name=name,
                type=type,
                board=board,
                product=product,
                platform=platform,
                version=version,
                description=description)
            if project is None:
                return { 'code': 500, 'content': { 'message': '项目已存在' } }
            
            permissions = [perm for perm in PermType]
            self.__perm_mgr.grant_user_permission(current_user, project.path, permissions)

            # 提交事务
            transaction.commit()
            return { 'code': 200, 'content': project.to_dict() }
            
        except Exception as e:
            logger.error(f'新建项目事务提交失败: {e}')
            # 回滚到保存点，恢复所有数据状态
            savepoint.rollback()
            return { 'code': 500, 'content': {'message': '新建项目事务提交失败' } }
        
    @api()
    def open_project(self, owner, name):
        """打开项目
        Args:
            owner (str): 项目所有者
            name (str): 项目名称
        Returns:
            Project: 打开的项目对象
        """
        current_user = self.__user_mgr.current_user()
        if current_user is None:
            return { 'code': 500, 'content': {'message': '无法获取当前用户信息' } }
        if not self.__perm_mgr.check_permission(current_user, f'{owner}/{name}', permissions=[PermType.READ]):
            return { 'code': 403, 'content': { 'message': '用户没有权限打开项目' } }
        project = self.__project_mgr.get_project(f'{owner}/{name}')
        if project is None:
            return { 'code': 404, 'content': { 'message': '项目不存在' } }

        return { 'code': 200, 'content': project.to_dict() }

    @api()
    def rename_project(self, owner, name, new_name):
        logger.error(f'method is empty: {__name__}.{__class__}.{__func__.__name__}')

    @api()
    def run_project(self, owner, name):
        logger.error(f'method is empty: {__name__}.{__class__}.{__func__.__name__}')
    
    @api()
    def stop_project(self, owner, name):
        logger.error(f'method is empty: {__name__}.{__class__}.{__func__.__name__}')

    @api()
    def set_project(self, owner, name):
        logger.error(f'method is empty: {__name__}.{__class__}.{__func__.__name__}')

    @api()
    def copy_project(self, owner, name):
        logger.error(f'method is empty: {__name__}.{__class__}.{__func__.__name__}')

    @api()
    def load_test_cases(self, owner, name):
        current_user = self.__user_mgr.current_user()
        if current_user is None:
            return { 'code': 500, 'content': {'message': '无法获取当前用户信息' } }
        if not self.__perm_mgr.check_permission(current_user, f'{owner}/{name}', permissions=[PermType.READ]):
            return { 'code': 403, 'content': {'message': '用户没有权限打开项目' } }
        project = self.__project_mgr.get_project(f'{owner}/{name}')
        if project is None:
            return { 'code': 404, 'content': {'message': '项目不存在' } }
        test_names = [ test_case.name for test_case in project.test_cases ]
        return { 'code': 200, 'content': test_names }

    @api()
    def new_test_case(self, owner: str, name: str, test: str, description: str):
        current_user = self.__user_mgr.current_user()
        if current_user is None:
            return { 'code': 500, 'content': {'message': '无法获取当前用户信息' } }
        if not self.__perm_mgr.check_permission(current_user, f'{owner}/{name}', permissions=[PermType.WRITE]):
            return { 'code': 403, 'content': {'message': '用户没有权限修改项目' } }
        project = self.__project_mgr.get_project(f'{owner}/{name}')
        if project is None:
            return { 'code': 404, 'content': {'message': '项目不存在' } }
        savepoint = transaction.savepoint()
        try:
            project.new_test_case(test, description)
            transaction.commit()
            return { 'code': 200, 'content': {} }
        except Exception as e:
            logger.error(f'新建测试用例事务提交失败: {e}')
            savepoint.rollback()
            return { 'code': 500, 'content': {'message': '新建测试用例事务提交失败' } }

    @api()
    def open_test_case(self, owner: str, name: str, test: str):
        current_user = self.__user_mgr.current_user()
        if current_user is None:
            return { 'code': 500, 'content': {'message': '无法获取当前用户信息' } }
        if not self.__perm_mgr.check_permission(current_user, f'{owner}/{name}', permissions=[PermType.READ]):
            return { 'code': 403, 'content': {'message': '用户没有权限打开项目' } }
        project = self.__project_mgr.get_project(f'{owner}/{name}')
        if project is None:
            return { 'code': 404, 'content': {'message': '项目不存在' } }
        test_case = project.get_test_case(test)
        if test_case is None:
            return { 'code': 404, 'content': {'message': '测试用例不存在' } }
        workscopes = [ workscope.to_dict() for workscope in test_case.workscopes ]
        return { 'code': 200, 'content': workscopes }

    @api()
    def get_activity_types(self):
        """获取活动类型配置"""
        try:
            return { 'code': 200, 'content': ActivityFactory.get_activity_types() }
        except Exception as e:
            logger.error(f'获取活动类型配置失败: {e}')
            return { 'code': 500, 'content': { 'message': f'获取活动类型配置失败: {e}' } }

    @api()
    def new_activity(self, owner: str, name: str, test: str, id: str, index: str, typename: str, inside: bool):
        current_user = self.__user_mgr.current_user()
        if current_user is None:
            return { 'code': 500, 'content': {'message': '无法获取当前用户信息' } }
        if not self.__perm_mgr.check_permission(current_user, f'{owner}/{name}', permissions=[PermType.WRITE]):
            return { 'code': 403, 'content': {'message': '用户没有权限修改项目' } }
        project = self.__project_mgr.get_project(f'{owner}/{name}')
        if project is None:
            return { 'code': 404, 'content': {'message': '项目不存在' } }
        test_case = project.get_test_case(test)
        if test_case is None:
            return { 'code': 404, 'content': {'message': '测试用例不存在' } }
        savepoint = transaction.savepoint()
        try:
            # 使用整数类型ID调用后端方法
            id = int(id) if id else None
            if not test_case.new_activity(id=id, index=int(index), typename=typename, is_inside=inside):
                return { 'code': 500, 'content': {'message': '父活动节点不存在：ID={id}' } }
            transaction.commit()
            return { 'code': 200, 'content': {} }
        except Exception as e:
            logger.error(f'新建测试用例事务提交失败: {e}')
            savepoint.rollback()
            return { 'code': 500, 'content': {'message': '新建测试用例事务提交失败' } }

            
############################################## 设备 API ##############################################    
    @api()
    def load_devices(self, force_reload=False):
        """加载设备列表（兼容性方法）"""
        return { 'code': 200, 'content': self.__device_proxy.load_devices(force_reload=force_reload) }

    @api()
    def capture_screen(self, sn: str, filename: str):
        """捕获屏幕"""
        image_url = self.__device_proxy.capture_screen(sn, filename)
        if image_url is None:
            return { 'code': 500, 'content': {'message': '捕获屏幕失败' } }
        return { 'code': 200, 'content': {'imageUrl': image_url} }
        
############################################## 模板 API ##############################################
    @api()
    def load_templates(self):
        logger.error(f'method is empty: {__name__}.{__class__}.{__func__.__name__}')

############################################## 任务 API ##############################################
    @api()
    def load_tasks(self):
        logger.error(f'method is empty: {__name__}.{__class__}.{__func__.__name__}')

############################################## 报告 API ##############################################
    @api()
    def load_reports(self):
        logger.error(f'method is empty: {__name__}.{__class__}.{__func__.__name__}')